#include "ampfrm.h"
#include <qwt_knob.h>
#include <qwt_thermo.h>
#include <qwt_round_scale_draw.h>
#include <qwt_math.h>
#include <qlayout.h>
#include <qlabel.h>
#include <qfont.h>
#include <qpen.h>
#include <qevent.h>

#if QT_VERSION < 0x040600
#define qFastSin(x) ::sin(x)
#define qFastCos(x) ::cos(x)
#endif

class Knob: public QWidget
{
public:
    Knob(const QString &title, double min, double max, QWidget *parent):
        QWidget(parent)
    {
        d_knob = new QwtKnob(this);
        d_knob->setRange(min, max, 0,1);
        d_knob->setScaleMaxMajor(10);

        d_knob->setKnobStyle(QwtKnob::Raised);
        d_knob->setKnobWidth(50);
        d_knob->setBorderWidth(2);
        d_knob->setMarkerStyle(QwtKnob::Notch);
        d_knob->setMarkerSize( 8 );

        d_knob->scaleDraw()->setTickLength( QwtScaleDiv::MinorTick, 4 );
        d_knob->scaleDraw()->setTickLength( QwtScaleDiv::MediumTick, 4 );
        d_knob->scaleDraw()->setTickLength( QwtScaleDiv::MajorTick, 6 );

        d_label = new QLabel(title, this);
        d_label->setAlignment(Qt::AlignTop | Qt::AlignHCenter);

        setSizePolicy(QSizePolicy::MinimumExpanding, 
            QSizePolicy::MinimumExpanding);
    }

    virtual QSize sizeHint() const
    {
        QSize sz1 = d_knob->sizeHint();
        QSize sz2 = d_label->sizeHint();

        const int w = qMax(sz1.width(), sz2.width());
        const int h = sz1.height() + sz2.height();
            
        int off = d_knob->scaleDraw()->extent(d_knob->font());
        off -= 10; // spacing

        return QSize(w, h - off);
    }

    void setValue( double value )
    {
        d_knob->setValue( value );
    }

    double value() const
    {
        return d_knob->value();
    }
    
protected:
    virtual void resizeEvent(QResizeEvent *e)
    {
        const QSize sz = e->size();

        int h = d_label->sizeHint().height();

        d_label->setGeometry(0, sz.height() - h,
            sz.width(), h);

        h = d_knob->sizeHint().height();
        int off = d_knob->scaleDraw()->extent(d_knob->font());
        off -= 10; // spacing

        d_knob->setGeometry(0, d_label->pos().y() - h + off,
            sz.width(), h);
    }

private:
    QwtKnob *d_knob;
    QLabel *d_label;
};

class Thermo: public QWidget
{
public:
    Thermo(const QString &title, QWidget *parent):
        QWidget(parent)
    {
        d_thermo = new QwtThermo( this );
        d_thermo->setPipeWidth( 6 );
        d_thermo->setRange( -40, 10 );
        d_thermo->setFillBrush( Qt::green );
        d_thermo->setAlarmBrush( Qt::red );
        d_thermo->setAlarmLevel( 0.0 );
        d_thermo->setAlarmEnabled( true );

        QLabel *label = new QLabel(title, this);
        label->setAlignment(Qt::AlignTop | Qt::AlignLeft);

        QVBoxLayout *layout = new QVBoxLayout(this);
        layout->setMargin(0);
        layout->setSpacing(0);
        layout->addWidget(d_thermo, 10);
        layout->addWidget(label);
    }

    void setValue(double value)
    {
        d_thermo->setValue(value);
    }

private:
    QwtThermo *d_thermo;
};

AmpFrame::AmpFrame(QWidget *p): 
    QFrame(p)
{
    d_knbVolume = new Knob("Volume", 0.0, 10.0, this);
    d_knbBalance = new Knob("Balance", -10.0, 10.0, this);
    d_knbTreble = new Knob("Treble", -10.0, 10.0, this);
    d_knbBass = new Knob("Bass", -10.0, 10.0, this);

    d_thmLeft = new Thermo("Left [dB]", this);
    d_thmRight = new Thermo("Right [dB]", this);

    QHBoxLayout *layout = new QHBoxLayout(this);
    layout->setSpacing(0);
    layout->setMargin(10);
    layout->addWidget(d_knbVolume);
    layout->addWidget(d_knbBalance);
    layout->addWidget(d_knbTreble);
    layout->addWidget(d_knbBass);
    layout->addSpacing(20);
    layout->addStretch(10);
    layout->addWidget(d_thmLeft);
    layout->addSpacing(10);
    layout->addWidget(d_thmRight);

    d_knbVolume->setValue( 7.0 );
    (void)startTimer(50);    
}

void AmpFrame::timerEvent(QTimerEvent *)
{
    static double phs = 0;
    
    //
    //  This amplifier generates its own input signal...
    //

    const double sig_bass = (1.0 + 0.1 * d_knbBass->value()) 
        * qFastSin(13.0 * phs);
    const double sig_mid_l = qFastSin(17.0 * phs);
    const double sig_mid_r = qFastCos(17.5 * phs);
    const double sig_trbl_l = 0.5 * (1.0 + 0.1 * d_knbTreble->value()) 
        * qFastSin(35.0 * phs);
    const double sig_trbl_r = 0.5 * (1.0 + 0.1 * d_knbTreble->value()) 
        * qFastSin(34.0 * phs);

    double sig_l = 0.05 * d_master * d_knbVolume->value() 
        * qwtSqr(sig_bass + sig_mid_l + sig_trbl_l);
    double sig_r = 0.05 * d_master * d_knbVolume->value()   
        * qwtSqr(sig_bass + sig_mid_r + sig_trbl_r);
    
    double balance = 0.1 * d_knbBalance->value(); 
    if (balance > 0) 
       sig_l *= (1.0 - balance);
    else
       sig_r *= (1.0 + balance);

    if (sig_l > 0.01)
       sig_l = 20.0 * log10(sig_l);
    else
       sig_l = -40.0;

    if (sig_r > 0.01)
       sig_r = 20.0 * log10(sig_r);
    else
       sig_r = - 40.0; 

    d_thmLeft->setValue(sig_l);
    d_thmRight->setValue(sig_r);

    phs += M_PI / 100;
    if (phs > M_PI) 
        phs = 0;
}

void AmpFrame::setMaster(double v)
{
    d_master = v;
}
